package ru.spb.osll.myfit.server.execute;

import org.apache.log4j.Logger;
import ru.spb.osll.myfit.server.Configurator;
import ru.spb.osll.myfit.server.MyFitClassLoaderFactory;
import ru.spb.osll.myfit.server.fixtureiterator.FileFactory;
import ru.spb.osll.myfit.server.parser.TableParser;
import ru.spb.osll.myfit.util.CollectionUtils;
import ru.spb.osll.myfit.util.FileHelper;
import ru.spb.osll.myfit.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URLClassLoader;
import java.util.*;

public class PageExecutorFactory {
  private Logger log = Logger.getLogger(PageExecutorFactory.class);
  private FileHelper m_helper;
  private final TableParser m_tableParser;
  private final String m_root;
  private final String m_fixtures;
  private final String m_jars;
  private final String m_execResults;
  private final String m_suiteResults;
  private final String m_files;
  // private String m_testLoggerFileName;
  private Configurator conf;

  public PageExecutorFactory(Configurator configurator, FileHelper helper) {
    m_tableParser = new TableParser();
    m_root = configurator.getRootDir();
    m_fixtures = configurator.getFixtureDir();
    m_jars = configurator.getDependenciesDir();
    m_execResults = configurator.getTestResultsDirectory();
    m_suiteResults = configurator.getSuiteResultsDirectory();
    m_files = configurator.getResources() + File.separator + "files";
    m_helper = helper;

    conf = configurator;
    // m_testLoggerFileName = (String)
    // configurator.getLoggerProperties().get("logger.file.name");

  }

  public Set<PageExecutor> createPageExecutorsForSuite(List<String> pagePaths, Map<String, String> metadata, String suiteResultId) throws Exception {
    String suiteBase = m_suiteResults + File.separator + suiteResultId;
    String root = suiteBase + File.separator + (new File(m_root)).getName();
    File fitNesseRoot = new File(root).getAbsoluteFile();

    m_helper.copyDir(m_root, suiteBase + File.separator + (new File(m_root)).getName());

    String fixtures = m_fixtures;
    String jars = m_jars;

    if (conf.getCopyDependencies()) {
      fixtures = suiteBase + File.separator + "fixtures";
      jars = suiteBase + File.separator + "jars";
      m_helper.copyDir(m_fixtures, fixtures);
      m_helper.copyDir(m_jars, jars);
    }

    Set<PageExecutor> executors = new LinkedHashSet<PageExecutor>();

    boolean deleteDependencies = false;

    Iterator<String> pagePathsIterator = pagePaths.iterator();
    while (pagePathsIterator.hasNext()) {
      String pagePath = pagePathsIterator.next();
      if (!pagePathsIterator.hasNext()) {
        deleteDependencies = conf.getCopyDependencies() && conf.getDeleteDependencies();
      }

      String base = m_helper.createUDir(m_execResults, pagePath);
      String uname = (new File(base)).getName();
      String date = StringUtils.substringFromPattern(FileHelper.FORMAT_PATTERN, uname, 2);
      Date startTime = StringUtils.dateFromString(date, FileHelper.DATE_FORMAT);

      String fileName = base + File.separator + pagePath + ".html";

      TestResultMetadataDao dao = new TestResultMetadataDao();
      String metadataFilePath = base + File.separator + pagePath + ".metadata";
      OutputStream stream = null;
      try {
        stream = m_helper.openFile(metadataFilePath);
        dao.save(metadata, stream);
      } catch(IOException e) {
      } finally {
        m_helper.close(stream);
      }

      URLClassLoader classLoader = createClassLoader(base, pagePath, jars, fixtures);
      FixtureListenerImpl fixtureListener = new FixtureListenerImpl(m_tableParser);
      FitNesseTestRunner runner = createTestRunner(fitNesseRoot.getParent(), fitNesseRoot.getName(), classLoader,
              fixtureListener);
      PageWriter pageWriter = new PageWriter(fileName, m_files);
      PageExecutor executor = new PageExecutor(runner, pagePath, fixtureListener, pageWriter, m_helper, startTime, uname, jars, fixtures, deleteDependencies);
      executor.setMetadata(metadata, metadataFilePath);
      executor.setContextClassLoader(classLoader);

      executors.add(executor);
    }

    return executors;
  }

  public PageExecutor createPageExecutor(String pagePath, Map<String, String> testRunMetadata) throws Exception {
    String base = m_helper.createUDir(m_execResults, pagePath);
    String uname = (new File(base)).getName();
    String date = StringUtils.substringFromPattern(FileHelper.FORMAT_PATTERN, uname, 2);
    Date startTime = StringUtils.dateFromString(date, FileHelper.DATE_FORMAT);
    String root = base + File.separator + (new File(m_root)).getName();
    m_helper.copyDir(m_root, root);
    String fileName = base + File.separator + pagePath + ".html";

    String fixtures = m_fixtures;
    String jars = m_jars;
  
    if (conf.getCopyDependencies()) {
      fixtures = base + File.separator + "fixtures";
      jars = base + File.separator + "jars";
      m_helper.copyDir(m_fixtures, fixtures);
      m_helper.copyDir(m_jars, jars);
    }
    
    TestResultMetadataDao dao = new TestResultMetadataDao();
    String metadataFilePath = base + File.separator + pagePath + ".metadata";
    OutputStream stream = null;
    try {
      stream = m_helper.openFile(metadataFilePath);
      dao.save(testRunMetadata, stream);
    } catch(IOException e) {
    } finally {
      m_helper.close(stream);
    }

    URLClassLoader classLoader = createClassLoader(base, pagePath, jars, fixtures);

    File fitNesseRoot = new File(root).getAbsoluteFile();
    FixtureListenerImpl fixtureListener = new FixtureListenerImpl(m_tableParser);
    FitNesseTestRunner runner = createTestRunner(fitNesseRoot.getParent(), fitNesseRoot.getName(), classLoader,
        fixtureListener);
    PageWriter pageWriter = new PageWriter(fileName, m_files);
    PageExecutor executor = new PageExecutor(runner, pagePath, fixtureListener, pageWriter, m_helper, startTime, uname, jars, fixtures, conf.getCopyDependencies() && conf.getDeleteDependencies());
    executor.setMetadata(testRunMetadata, metadataFilePath);
    executor.setContextClassLoader(classLoader);

    return executor;
  }

  protected FitNesseTestRunner createTestRunner(String rootPath, String rootDirectoryName, ClassLoader classLoader,
      ProgressFixtureListener listener) throws Exception {
    return new FitNesseTestRunnerProxy(rootPath, rootDirectoryName, classLoader, listener);
  }

  private URLClassLoader createClassLoader(String base, String pagePath, String depsPath, String fixturePath) throws IOException {
    String dirName = (new File(base)).getName();

    List<String> dirs = CollectionUtils.list(depsPath, fixturePath);

    String logDir = null;

    try {
      logDir = createLogConfigFile(base, dirName, pagePath);
    } catch (Throwable e) {
      log.warn("Logfile for " + dirName + " have not been created", e);
    }

    URLClassLoader classLoader;

    if (logDir == null) {
      classLoader = new MyFitClassLoaderFactory(new FileFactory()).createClassLoader(dirs.toArray(new String[dirs.size()]));
    } else {
      classLoader = new MyFitClassLoaderFactory(new FileFactory()).createClassLoader(
          CollectionUtils.list(new File(logDir).toURI().toURL()), dirs.toArray(new String[dirs.size()]));
    }

    return classLoader;
  }

  /**
   * 
   * @param destDir
   *          - directory testExecutionResult/PageName
   * @param pageDir
   *          - name of directory for current executed page
   * @param pagePath
   * @return name of directory for log configuration file
   * @throws IOException
   *           - creating log configuration file failed
   */
  private String createLogConfigFile(String destDir, String pageDir, String pagePath) throws IOException {
    String configDir = destDir + File.separator + "config";
    String configFile = configDir + File.separator + "log4j.properties";

    m_helper.createDir(configDir);

    OutputStream out = m_helper.openFile(configFile);
    OutputStreamWriter writer = new OutputStreamWriter(out);
    // Never change symbol "/" to File.separator. Resource bundle will use this
    // line.
    Properties prop = conf.getLoggerProperties();
    String loggerFileName = (String) prop.get("logger.file.name");
    String loggerLevel = (String) prop.get("logger.level");

    String loggerFile;
    if (loggerFileName != null && loggerFileName.length() != 0) {
      loggerFile = m_execResults + "/" + pageDir + "/" + loggerFileName;
    } else {
      loggerFile = m_execResults + "/" + pageDir + "/" + pagePath + ".log";
    }
    writer.write("log4j.rootLogger=" + (loggerLevel != null ? loggerLevel : "debug") + ", file\n");
    writer.write("log4j.debug=true\n");
    writer.write("log4j.appender.file.Append=false\n");
    writer.write("log4j.appender.file=org.apache.log4j.FileAppender\n");
    writer.write("log4j.appender.file.file=" + loggerFile + "\n");
    writer.write("log4j.appender.file.layout=org.apache.log4j.PatternLayout\n");
    writer.write("log4j.appender.file.layout.conversionPattern=%d{ABSOLUTE} %5p  %C{1}:%L - %m%n\n");

    writer.close();
    out.close();
    return configDir;
  }

}
